[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください
この記事では、Scala 3で導入される「トップレベル定義」と、廃止予定のパッケージオブジェクトについて解説します。
変数や型エイリアスなどもトップレベルに定義できるようになる
Scala 3では、「トップレベル定義」が強化されました。
これまではパッケージやインポート、クラス、オブジェクトなどが定義できましたが、さらに変数、メソッド、型エイリアスなども定義できるようになりました。
以下のように、トップレベル定義を複数のソースファイルに分散することができます。
つまり、どのファイルにおいてもトップレベルに定義を記述することができるということです。
// first.scala package com.scalapedia type Tag[T] = (String, T) val a: Tag[Int] = ("version", 3) def value = a._2
// second.scala package com.scalapedia case class C() implicit object Cops { extension (x: C) def pair(y: C) = (x, y) }
トップレベルには以下のような定義を記述することができます。
- すべてのパターン、値、メソッド、および型の定義
- 暗黙のクラスやオブジェクト
- 不透明型のエイリアスのコンパニオンオブジェクト
トップレベルに記述されたオブジェクト等は、当該パッケージに直接置かれているものとして扱われます。
アクセス修飾子も使える
private
などのアクセス修飾子も、通常と同様に使用できます。
つまり、例えばprivate
なオブジェクトは当該パッケージ内でのみアクセス可能、といった形です。
同名の定義は同じファイルに記述する
また、複数のトップレベル定義を同じ名前にしてオーバーロードしようとしている場合は、すべて同じファイルに記述してください。
例えば上述の例のようにfirst.scala
に既にdef value
が定義されている場合には、second.scala
に引数の数を変えた別のdef value
を定義することはできないということです。
また、second.scala
に既にcase class C
が定義されている場合には、first.scala
に別途object C
を定義することもできません。
パッケージオブジェクトは歴史的使命を終えて廃止される
Scala 2.8より導入されたパッケージオブジェクトは、一つのパッケージにつき一つのパッケージオブジェクトを作成すると、この中にあらゆる種類の定義を記述できるという機能でした。
Scala 3より、複数のファイルに様々な種類の定義をトップレベルで記述できるようになったため、パッケージオブジェクトはもはや必要なくなりました。
つまり、パッケージオブジェクトは歴史的使命を終えたということです。
削除される具体的な時期は未定
パッケージオブジェクトはこの先廃止されることが決まっています。
Scala 3.0の段階ではまだ利用可能ですが、3系のいずれかのタイミングで非推奨とされ、最終的には削除される予定です。
すぐ移行しなければならないわけではありませんが、今後の動向には留意しておきましょう。
トップレベル定義の実体
トップレベル定義によって、これらの定義をラップするオブジェクトが生成されます。
com.scalapedia
パッケージ内のSample.scala
というファイルにトップレベル定義を記述すると、Sample$package
というオブジェクトが生成され、この中に値などが置かれます。
このオブジェクトに置かれたメンバには透過的にアクセスできることになっています。
つまり、これらの定義はcom.scalapedia
に置かれているメンバとして扱うことができるというわけです。
ファイル名がバイナリ互換性に影響する可能性がある
このことは、バイナリ互換性に関して注意する必要があることを意味します。
トップレベル定義を含むソースファイルの名前を変更すると、生成されるオブジェクトとそのクラスの名前も変更されます。
つまりバイナリ互換性が崩れるのです。
特にライブラリを提供している方は注意しましょう。
mainメソッドをトップレベルに配置するのは勧められない
以下のようなmainメソッドをトップレベルに宣言することも「技術的には」可能です。
def main(args: Array[String]): Unit = ...
ただし、呼び出し方がややこしくなってしまいます。
mainメソッドをMain.scala
のトップレベルに定義した場合、例えばコマンドラインからは scala Main$package
といった形で呼び出すことになります。
つまり呼び出し対象の名前が、トップレベル定義の実体を知っている人にしかわからない珍妙な名前になってしまうので、おすすめしません。
mainメソッドは明示的に命名されたオブジェクトに定義しましょう。
まだ移行しなくてOK
パッケージオブジェクトは将来的に廃止される予定ですが、当面は使用を続けることができます。
新しく定義する場合はトップレベルに定義しましょう。